package com.orange.framework.config;

import com.orange.framework.consumer.annotation.EnableAgent;
import com.orange.framework.consumer.annotation.ConsumerClient;
import com.orange.framework.consumer.factor.ProxyClientFactorBean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.*;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 方式二
 * 扫描类注入
 */
@Slf4j
public class ProxyClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

    //资源加载器
    private ResourceLoader resourceLoader;

    //环境变量
    private Environment environment;

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }


    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
        //构建一个classPath扫描器
        ClassPathScanningCandidateComponentProvider scanner = getScanner();
        scanner.setResourceLoader(this.resourceLoader);
        //添加注解类过滤器
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(ConsumerClient.class);
        scanner.addIncludeFilter(annotationTypeFilter);
        //获取需要扫描的包路径，添加到List数组
        List<String> basePackages = this.scannerPackages(metadata);
        //注册当前包里的所有符合要求的bean
        this.beanDefinitionRegistry(basePackages, scanner, registry, importBeanNameGenerator);
    }

    /**
     * 类扫描器
     *
     * @return
     */
    protected ClassPathScanningCandidateComponentProvider getScanner() {
        return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
            @Override
            protected boolean isCandidateComponent(
                    AnnotatedBeanDefinition beanDefinition) {
                boolean isCandidate = false;
                if (beanDefinition.getMetadata().isIndependent()) {
                    if (!beanDefinition.getMetadata().isAnnotation()) {
                        isCandidate = true;
                    }
                }
                return isCandidate;
            }
        };
    }

    /**
     * 获取需要扫描的包路径，添加到List数组
     *
     * @param metadata
     * @return
     */
    private List<String> scannerPackages(AnnotationMetadata metadata) {
        List<String> basePackages = new ArrayList<>();
        /**
         * 获取包名不一致，导致扫不到包时，需要使用@EnableProxy注解，指定包名
         *  com.orange  com.orange.agent  这样的包名是不需要指定@EnableProxy 或者@EnableProxy默认包名为com.orange，否则会导致Bean重复注册
         *  com.orange  com.agent 这种包名是需要单独指定的@EnableProxy，并指定包名
         */
        Map<String, Object> attributes = metadata.getAnnotationAttributes(EnableAgent.class.getCanonicalName());
        for (String pkg : (String[]) attributes.get("basePackages")) {
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
        //添加当前包
        String currentPackage = ClassUtils.getPackageName(metadata.getClassName());
        if (!basePackages.contains(currentPackage)) {
            basePackages.add(currentPackage);
        }
        return basePackages;
    }

    /**
     * 扫描出来的符合要求的所有bean注册为spring管理的bean
     *
     * @Author YWF
     * @Date 2023/2/2 10:49
     * @Param [basePackages, scanner, registry, importBeanNameGenerator]
     **/
    private void beanDefinitionRegistry(List<String> basePackages, ClassPathScanningCandidateComponentProvider scanner, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
        for (String basePackage : basePackages) {
            Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
            if (!candidateComponents.isEmpty()) {
                for (BeanDefinition candidateComponent : candidateComponents) {
                    //注册为spring管理的bean
                    if (candidateComponent instanceof AnnotatedBeanDefinition) {
                        String beanClassName = candidateComponent.getBeanClassName();
                        //持Spring的多个相同类型的bean时，使用@Primary来赋予bean更高的优先级
                        AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                        AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata();
                        Assert.isTrue(annotationMetadata.isInterface(),
                                "@ProxyClient只能在接口上指定。错误指定类[ {" + beanClassName + "} ]");
                        Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(ConsumerClient.class.getCanonicalName());
                        try {
                            // 这里如果直接使用Class.forName(className) 可能会找不到类
                            Class<?> clazz = resourceLoader.getClassLoader().loadClass(beanClassName);
                            if (isNotNeedProxy(clazz)) {
                                log.info("排除代理类：{}", beanClassName);
                                continue;
                            }
                            //注册Bean
                            this.registryBean(registry, clazz, attributes);
                        } catch (ClassNotFoundException e) {
                            log.error("注册bean:{}失败！", beanClassName);
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    /**
     * 注册Bean
     *
     * @param registry
     * @param clazz
     */
    private void registryBean(BeanDefinitionRegistry registry, Class<?> clazz, Map<String, Object> attributes) {
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(ProxyClientFactorBean.class);
        String agentBeanName = clazz.getSimpleName();
        String beanName = agentBeanName.substring(0, 1).toLowerCase() + agentBeanName.substring(1);
        //这里还可以设置依赖项，是否懒加载，构造方法参数等等与类定义有关的参数
        beanDefinitionBuilder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();

        //支持Spring的多个相同类型的bean时，使用@Primary来赋予bean更高的优先级
        boolean primary = (Boolean) attributes.get("primary");
        beanDefinition.setPrimary(primary);

        //自动注入类型设置
        beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, clazz.getName());
        beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(clazz.getName());
        BeanDefinitionHolder beanDefinitionHolder = new BeanDefinitionHolder(beanDefinition, beanName);
        BeanDefinitionReaderUtils.registerBeanDefinition(beanDefinitionHolder, registry);
        log.info("注册代理[{}]的代理bean...", agentBeanName);
    }

    /**
     * 否是需要被代理的接口
     *
     * @param beanClazz
     * @return
     */
    private boolean isNotNeedProxy(Class beanClazz) {
        // 如果不是接口,或者没有添加@NeedProxy注解,则说明不是需要被代理的接口
        return !beanClazz.isInterface() || null == AnnotatedElementUtils.findMergedAnnotation(beanClazz, ConsumerClient.class);
    }


}
